package com.ar4j.sql;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.jdbc.core.StatementCreatorUtils;
import org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource;

import com.ar4j.ActiveRecord;
import com.ar4j.ActiveRecordMetadata;
import com.ar4j.type.ITypeConverter;

/**
 * A sql parameter source that is able to retrieve parameter values from an 
 * active record instance
 */
public class ActiveRecordSqlParameterSource extends AbstractSqlParameterSource {
  private ActiveRecord<?> record;
  private Map<String, String> columnToPropertyMap;
  
  /**
   * Create a new parameter source using the given record to retrieve parameter values.
   */
  public ActiveRecordSqlParameterSource(ActiveRecord<?> record, ActiveRecordMetadata metadata) {
    this.record = record;
    
    this.columnToPropertyMap = new HashMap<String, String>();
    for(Map.Entry<String, String> entry : metadata.getPropertyColumnNameMap().entrySet()) {
      columnToPropertyMap.put(entry.getValue(), entry.getKey());
    }
  }

  /**
   * @return true if the active record given during construction contains the given parameter
   * name as either a property or a mapped column name
   */
  @Override
  public boolean hasValue(String paramName) {
    return (record.getPropertyNames().contains(paramName) || columnToPropertyMap.containsKey(paramName));
  }
  
  /**
   * @return the value of the parameter. The value is found as a property value of the
   * active record given during construction, as either its property or a mapped column name.
   * Type conversions are applied to the active record value as needed.
   */
  @SuppressWarnings("unchecked")
  @Override
  public Object getValue(String paramName) throws IllegalArgumentException {
    String property = paramName;
    if(!record.getPropertyNames().contains(paramName)) {
      property = columnToPropertyMap.get(paramName);
    }
    
    Object value = record.getProperty(property);
    ITypeConverter<Object, Object> converter = (ITypeConverter<Object, Object>) record.getPropertyTypeConverter(property);
    if(converter != null) {
      value = converter.convertToStorage(value);
    }
    
    return value;
  }

  /**
   * @return the sql type of the given parameter name. The type of the value is found
   * in the active record given during the construction of this parameter source and is resolved
   * to the sql type.
   */
  @Override
  public int getSqlType(String paramName) {
    String property = paramName;
    if(!record.getPropertyNames().contains(paramName)) {
      property = columnToPropertyMap.get(paramName);
    }
    
    int sqlType = super.getSqlType(property);
    if (sqlType != TYPE_UNKNOWN) {
      return sqlType;
    }
    
    Class<?> type = record.getPropertyType(property);
    ITypeConverter<?, ?> converter = record.getPropertyTypeConverter(property);
    if(converter != null) {
      type = converter.getStorageType();
    }
    
    return StatementCreatorUtils.javaTypeToSqlParameterType(type);
  }
  
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }
  
}
